package qq2564874169.tuaotuao.fx.file;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPSClient;
import qq2564874169.tuaotuao.fx.FxAssert;
import qq2564874169.tuaotuao.fx.utils.ExUtil;
import qq2564874169.tuaotuao.fx.utils.FileUtil;
import qq2564874169.tuaotuao.fx.utils.StreamUtil;
import qq2564874169.tuaotuao.fx.utils.StringUtil;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.UUID;

public class FtpFilespace implements Filespace {
    public enum SSLOption {
        None, TLS
    }
    private String domain;
    private FTPClient ftp;
    private String cacheDir;

    public FtpFilespace(String host, String username, String passwd) {
        int port = 21;
        if (host.contains(":")) {
            var idx = host.indexOf(":");
            port = Integer.parseInt(host.substring(idx + 1));
            host = host.substring(0, idx);
        }
        init(host, port, username, passwd, SSLOption.None);
    }

    public FtpFilespace(String address, Integer port, String username, String passwd) {
        init(address, port, username, passwd, SSLOption.None);
    }

    public FtpFilespace(String address, Integer port, String username, String passwd, SSLOption ssl) {
        init(address, port, username, passwd, ssl);
    }

    private void init(String address, Integer port, String username, String passwd, SSLOption ssl) {
        try {
            if (ssl == SSLOption.TLS) {
                ftp = new FTPSClient("TLS", false);
            } else {
                ftp = new FTPClient();
            }
            ftp.connect(address, port);
            ftp.login(username, passwd);

            if (ssl == SSLOption.TLS) {
                var sslContext = SSLContext.getInstance("TLS");
                var trustManager = new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {

                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                };
                sslContext.init(null, new TrustManager[]{trustManager}, null);
                ftp.setSocketFactory(sslContext.getSocketFactory());
                ftp.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
                ((FTPSClient) ftp).execPBSZ(0);
                ((FTPSClient) ftp).execPROT("P");
            }
            ftp.setControlEncoding("UTF-8");
            ftp.enterLocalPassiveMode();
            ftp.setFileType(FTP.BINARY_FILE_TYPE);
            ftp.changeWorkingDirectory("/");
        } catch (Exception e) {
            throw ExUtil.toRuntime(e);
        }
    }

    public void setCacheDir(String dir) {
        this.cacheDir = dir;
    }

    public void setDomain(String domain) {
        if (domain != null && domain.endsWith("/")) {
            this.domain = StringUtil.trimRight(domain, "/");
        }
    }

    private static String enstr(String str) {
        return new String(str.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
    }

    private static String checkPath(String path) {
        FxAssert.isTrue(path.startsWith("/"), "文件路径必须以/开头");
        return path;
    }

    private void makedir(String path) {
        var split = checkPath(path).substring(1).split("/");
        try {
            if (split.length > 1) {
                var pdir = path.substring(0, path.lastIndexOf("/") + 1);
                if (ftp.changeWorkingDirectory(pdir) == false) {
                    var newdir = "";
                    for (int i = 0; i < split.length - 1; i++) {
                        newdir += ("/" + split[i]);
                        ftp.makeDirectory(newdir);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void save(String path, InputStream stream) {
        makedir(path);
        try {
            if (!ftp.storeFile(enstr(path), stream)) {
                throw new RuntimeException("上传失败");
            }
        } catch (IOException e) {
            throw ExUtil.toRuntime(e);
        }
    }

    @Override
    public String getUri(String path) {
        checkPath(path);
        if (domain != null) {
            path = domain + path;
        }
        return path;
    }

    @Override
    public InputStream getFile(String path) {
        checkPath(path);
        try {
            var is = ftp.retrieveFileStream(enstr(path));
            return new DataInputStream(is) {
                @Override
                public void close() throws IOException {
                    super.close();
                    ftp.completePendingCommand();
                }
            };
        } catch (IOException e) {
            throw ExUtil.toRuntime(e);
        }
    }

    @Override
    public void remove(String path) {
        checkPath(path);
        try {
            ftp.deleteFile(enstr(path));
        } catch (IOException e) {
            throw ExUtil.toRuntime(e);
        }
    }

    @Override
    public void move(String src, String dest) {
        makedir(src);
        makedir(dest);
        try {
            var file = ftp.mdtmFile(enstr(src));
            if (file == null) {
                throw new RuntimeException("文件不存在:" + src);
            }
            if (!ftp.rename(enstr(src), enstr(dest))) {
                throw new RuntimeException("移动失败");
            }
        } catch (IOException e) {
            throw ExUtil.toRuntime(e);
        }
    }

    @Override
    public void copy(String src, String dest) {
        makedir(src);
        makedir(dest);
        try {
            OutputStream os;
            File cacheFile = null;
            if (cacheDir != null) {
                var filename = FileUtil.combine(cacheDir, UUID.randomUUID().toString());
                cacheFile = new File(filename);
                if (cacheFile.createNewFile()) {
                    throw new RuntimeException("文件创建失败");
                }
                os = new FileOutputStream(cacheFile);
            } else {
                os = new ByteArrayOutputStream();
            }
            try (var inp = getFile(src)) {
                var buf = new byte[4096];
                int len = -1;
                while ((len = inp.read(buf)) > 0) {
                    os.write(buf, 0, len);
                }
                if (os instanceof FileOutputStream) {
                    os.close();
                }
            }
            InputStream is;
            if (cacheFile != null) {
                is = new FileInputStream(cacheFile);
            } else {
                is = StreamUtil.convert((ByteArrayOutputStream) os);
            }
            try (var out = ftp.storeFileStream(enstr(dest))) {
                FxAssert.isNotNull(out, "上传流获取失败");
                var buf = new byte[4096];
                int len = -1;
                while ((len = is.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
            } finally {
                is.close();
                if (cacheFile != null) {
                    cacheFile.delete();
                }
            }
            ftp.completePendingCommand();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
